#ifndef RS_DICT_H
#define RS_DICT_H
#include <set>
#include <algorithm>
#include "rs_string.h"
#include "rs_table.h"
#include "rs_regex.h"
#include "rs_convert.h"

class pron_ography {
    // pronunciation and orthography regexes
public:
    pron_ography (const rs_string& str_to, const rs_string& str_pr, unsigned priority) :
      po_to  (L"^"+nth_substring (str_to, 0, L">"), &cmudict_charset),
      po_to2 (L"^"+nth_substring (str_to, 1, L">"), &cmudict_charset),
      po_pr  (L"^"+nth_substring (str_pr, 0, L">"), &cmudict_charset),
      po_pr2 (L"^"+nth_substring (str_pr, 1, L">"), &cmudict_charset),
      po_priority (priority)
      {
          po_to_len = po_to.get_length();
          po_pr_len = po_pr.get_length();
          po_to_full = po_to_len + po_to2.get_length();
          po_pr_full = po_pr_len + po_pr2.get_length();
      };
      bool matches (const rs_string& word_to, const rs_string& word_pr) const {
          return 
              po_to.matches (word_to) &&
              po_pr.matches (word_pr) &&
              po_to2.matches (word_to.substr(po_to_len)) &&
              po_pr2.matches (word_pr.substr(po_pr_len));
      }
      bool longer (const pron_ography& a) const {
          // returns true if "this" is longer than "a"
          return po_to_full> a.po_to_full && po_pr_full>=a.po_pr_full ||
                 po_to_full>=a.po_to_full && po_pr_full> a.po_pr_full;
      }
      rs_string get_str() const;
      unsigned get_priority() const { return po_priority; };
      unsigned get_po_to_len() const { return po_to_len; };
      unsigned get_po_pr_len() const { return po_pr_len; };
private:
    rs_regex po_to; // TO (traditional orthography), searched and processed
    rs_regex po_to2; // TO, required continuation context
    rs_regex po_pr; // PR (pronunciation), searched and processed
    rs_regex po_pr2; // PR, required continuation context
    unsigned po_to_len; // length of compiled regex for TO
    unsigned po_pr_len;
    unsigned po_to_full; // length of compiled regex for TO
    unsigned po_pr_full;
    unsigned po_priority; // 1: tried first; 2: tried next
};


enum conv_flags {
    cf_none=0,
    cf_start_word=0x1,  // conversion can take place only at start of word
    cf_end_word=0x2,  // conversion can take place only at end of word
    cf_before_stress=0x4,  // conversion can take place only before stressed nibble; not yet implemented
    cf_after_stress=0x8,  // conversion can take place only after stressed nibble; not yet implemented
};


class ro_conv_search {
    // conversion search condition
public:
    ro_conv_search (rs_string search);
    const vector<rs_regex>& get_search_v() const {return rocf_search_v; };
    unsigned get_search_nsize () const { return rocf_nsize; };
    const rs_string& get_wflag () const { return rocf_wflag; };
    rs_string ro_conv_search::search_str() const;
    bool matches (const rs_string_v& nibble_v, unsigned start) const;
private:
    vector<rs_regex>  rocf_search_v;
    unsigned          rocf_nsize;  // nibble-vector size; cached rocf_search_v.size()
    rs_string         rocf_wflag;  // word flag; e.g. L"puk" or L"pus": only words with such flag are included
    unsigned          rocf_nflags; // nibble flags; values of type conv_flags
};



class ro_conv_rule {
    // conversion rule parsed from
    //   e.g. "ie_eh e" and associated ":option name"
    //   more detailed find can be "t_sh/io_ah/n_n shun" with slashes "/" separating nibbles
    //     or "a_ae|puk:a_aa  aa" that specifies "bath" lexical set
    // and activation of option
public:
    ro_conv_rule (
        const rs_string& search,
        const rs_string& replace,
        const rs_string& option);
    bool get_active () const { return roc_active; };
    void set_active (bool active) { roc_active = active; };
    const vector<rs_regex>& get_search_v(unsigned ix=0) const {return roc_search_v[ix].get_search_v(); };
    rs_string search_str(unsigned ix=0) const { return roc_search_v[ix].search_str(); };
    const rs_string& get_replace() const {return roc_replace; };
    const rs_string& get_option() const {return roc_option; };
    bool matches (const rs_string_v& rs_string_v, unsigned start, unsigned ix=0) const 
    { 
        return roc_search_v[ix].matches (rs_string_v, start);
    };
    unsigned get_search_nsize (unsigned ix=0) const { return roc_search_v[ix].get_search_nsize(); };
    const rs_string& get_wflag (unsigned ix) const { return roc_search_v[ix].get_wflag(); };
    unsigned get_search_count () const { return roc_search_v.size(); };
private:
    rs_string  roc_option;
    bool       roc_active;
    vector<ro_conv_search> roc_search_v;
    rs_string  roc_replace;
};


class ro_option {
public:
    ro_option (const rs_string& name);
    void add_word (const rs_string& word_to, const rs_string& word_ro);
    void add_affix (const rs_string& affix);
    const rs_string& get_name() const { return roo_name; };
    const rs_string& get_nd_name() const { return roo_nd_name; };
    const rs_table& get_word_rst() const { return roo_word; };
    const rs_table& get_affix_rst() const { return roo_affix; };
private:
    rs_string roo_name;   // name as specified in _pron_conv.rst file
    rs_string roo_nd_name;  // name without dashes
    rs_table roo_word;  // 2 textual columns; specially treated words
    text_cix to_cix; // column in roo_word with TO words
    text_cix ro_cix; // column in roo_word with RO words
    rs_table roo_affix; // 1 textual column; specially treated affixes
    text_cix affix_cix; // column in roo_affix with affixes
};

class pobro {
    // pronunciation-orthography based respelled orthograpy
    // used for creating xx_pron_dict.rst file and for
    // creating RO dictionaries based on it
public:
    bool parse_pron_conv (const rs_string& pron_conv_fname);
    bool skipped (const rs_string& word_to) const {
        return find (pob_skipped.begin(), pob_skipped.end(), word_to) != pob_skipped.end();
    }
    bool relate (
        const rs_string& word_to,
        const rs_string& word_pr,
        rs_string& relation) const;
    void convert_to_ro (
        const rs_string& word_to,
        rs_string word_rel,
        rs_string& word_ro,
        unsigned& nib_chan,
        unsigned& nib_all,
        const rs_string& stress,
        const rs_table& pron_rst) const {
            bool used_dummy;
            rs_string nibble_dummy;
            convert_to_ro_base (word_to, word_rel, word_ro, nib_chan, nib_all, 0, used_dummy, nibble_dummy, stress, pron_rst);
    }
    void convert_stat (
        const rs_string& word_to,
        rs_string word_rel,
        rs_string& word_ro,
        unsigned used_conv_ix,
        bool& used,
        rs_string& nibble,
        const rs_table& pron_rst) const
    {
            unsigned nib_chan, nib_all;  // only as argument to convert_to_ro_base
            convert_to_ro_base (word_to, word_rel, word_ro, nib_chan, nib_all, used_conv_ix, used, nibble, L"", pron_rst);
    }

    const vector<ro_conv_rule>& get_conv_v () const { return pob_conv; };
    const rs_string_v& get_option_group_v() const { return pob_opt_gr_v; };
    void enable_options (rs_string_v option_v); // passing by value intentionally
    bool option_exists (const rs_string& option);
    rs_string_v get_all_options() const;
    const rs_table& get_word_rst() const { return pob_word; };
    const rs_table& get_affix_rst() const { return pob_affix; };
private:
    bool relate_rec (
        const rs_string word_to, // remaining TO word to be related
        const rs_string word_pr, // remaining PR word to be related
        unsigned priority,       // use only rules with roc_priority <= priority
        rs_string& relation,      // if relation succeded, here is the result; if failed, shows the failure
        unsigned& max_len_failed,
        rs_string& failed) const;
    void sort_pob_rel ();
    void log_pob_rel (const rs_string& title) const;
    ro_option& get_option (const rs_string& option, bool no_dashes=false);
    void convert_to_ro_base (
        const rs_string& word_to,
        rs_string word_rel,
        rs_string& word_ro,
        unsigned& nib_chan,
        unsigned& nib_all,
        unsigned used_conv_ix,
        bool& used,
        rs_string& nibble,
        const rs_string& stress,
        const rs_table& pron_rst) const;

    // the following 2 variables are used when creating _pron_dict.rst file
    vector<pron_ography> pob_rel; // relations between TO and orthography
    set<rs_string> pob_skipped; // skipped words from pronunciation dictionary, in TO

    // the next variables are used when creating RO dictionary
    vector<ro_conv_rule> pob_conv;    // parsed from "option:" sections, subsection "convert:"
    vector<ro_option> pob_option_v;
    // pob_opt_gr_v is derived from names of pob_option_v
    rs_string_v pob_opt_gr_v;    // option groups; if dash is present, text before dash; if no dash, whole name;
      // so e.g. L"e" or L"u"
    rs_string   pob_stress_pairs; // pairs of characters, all concatenated; first char unstressed, second stressed
    rs_string   pob_stress_option;  // special option that controls stress marking
    bool        pob_mark_stress;  // true if pob_stress_option is enabled

    rs_table    pob_word; // concatenated tables roo_word from all pob_option_v that are enabled
    rs_table    pob_affix; // concatenated tables roo_affix from all pob_option_v that are enabled
};


class import {
public:
    import (
        const rs_string& out_fname,
        const rs_string& abbr,
        const rs_string& name,
        const rs_string& tag) :
            imp_out_rst (abbr, name, tag),
            imp_out_fname (out_fname+L".rst")
        {
            imp_abbr = abbr;
            imp_tag = tag;
            imp_cix_or_to = imp_out_rst.add_text_col (L"OR_TO", true, 15, false);
            imp_or_ro_str = L"OR_"+imp_out_rst.get_abbr();
            imp_cix_or_ro = imp_out_rst.add_text_col (imp_or_ro_str, true, 15, false);
            imp_cix_freq  = imp_out_rst.add_numb_col (L"FREQN", 8, false);
            imp_cix_flags = imp_out_rst.add_text_col (L"FLAGS", false, 13, false);
            imp_cix_comp = imp_out_rst.add_text_col (L"COMP", false, 15, false);
            imp_cix_stress = imp_out_rst.add_text_col (L"STRESS", false, 10, false);
            imp_cix_freqx  = imp_out_rst.add_numb_col (L"FREQX", 8, false);
            imp_cix_wf_size = imp_out_rst.add_numb_col (L"WF_SIZE", 2, false);
            imp_cix_wf_base = imp_out_rst.add_text_col (L"WF_BASE", false, 10, false);
            imp_cix_affixes = imp_out_rst.add_text_col (L"AFFIXES", false, 5, false);
        };
    bool pron_dict_combine (
        const rs_string& us_fname,
        const rs_string& uk_fname,
        const rs_string& freq_fname,
        const rs_string& pron_conv_fname,
        const rs_string& case_fname,
        const rs_string& affix_cg_fname,
        const rs_string& start_word,
        unsigned row_limit,
        bool incl_us,
        bool incl_uk);
    bool convert_d50 (
        const rs_string& in_fname, 
        const rs_string& freq_fname);
private:
    bool convert_load_freq (const rs_string& freq_fname);
    void extract_stress (rs_string& word_pr, rs_string& stress_data);
    bool convert_one_pron (const rs_string& in_fname,
        const rs_string& us_uk_flag,
        const pobro& pob,
        const rs_table& case_rst,
        const rs_affix& aff);
    // input dictionary
    rs_istream imp_ifs;
    // frequency table
    rs_table imp_freq_rst;
    // output table
    rs_table imp_out_rst;
    rs_string imp_out_fname;  // file name of complete dictionary
    //rs_string imp_out_base_fname;  // file name of dictionary with basic forms only
    rs_string imp_start_word; // words before this word (in order ass in pronouncing table) are skipped; for debugging
    unsigned imp_row_limit;  // if >0, only process so many rows from each pronuncing table; for debugging
    rs_string imp_abbr;
    rs_string imp_tag;
    // column indexes in dictionary tables
    text_cix imp_cix_or_to;
    text_cix imp_cix_or_ro;
    numb_cix imp_cix_freq;
    //numb_cix imp_cix_frc;
    text_cix imp_cix_flags;
    text_cix imp_cix_stress; // nibble stress levels: [012] for vocals, [-] for others
    text_cix imp_cix_comp; // composition of word by affixes (one level only) or joining two words together
    numb_cix imp_cix_freqx; // for non-composed words, sum of frequencies of all derived words
    // (of variation 1 of the same origin uk/us); for other, zero
    numb_cix imp_cix_wf_size; // word-family size, number of members
    text_cix imp_cix_wf_base; // base word of word family
    text_cix imp_cix_affixes; // affixes, all levels
    rs_string imp_or_ro_str; // name of column imp_cix_or_ro
    numb_cix imp_freq_all_cix;  // column FR_ALL in frequency table 
    numb_cix imp_freq_lc_cix;   // column FR_LC in frequency table 
    numb_cix imp_freq_frc_cix;  // column FREQL, added to frequency table

};


class dcreate {
    // creating RO dictionary based on spelling dictionary
public:
    dcreate () {};
    bool create_top (
        const rs_string& bpd_name,
        rs_string options,
        const rs_string& cdir,
        const rs_string& out_ro_name,
        const rs_string& pron_conv_fname,
        const rs_string& pron_dict_fname,
        bool keep,
        const rs_string& affix_cg_fname,
        const rs_string& proper_lc_fname,
        const rs_string& sort_order,
        bool common_only,
        const rs_string& rflag,
        const rs_string& kaffixes,
        unsigned min_freqx,
        const rs_string& debugw);
    bool show_statistics (const rs_string& bpd_name,
        const rs_string& pron_conv_fname,
        const rs_string& pron_dict_fname,
        const rs_string& sort,
        const rs_string& coptions,
        bool a_export);
private:
    void create_rec (rs_string_v combining_v,
        rs_string_v used_v,
        bool plain_ro_dict_name);
    void create_base (const rs_string& out_name_infix);
    bool init_tables (const rs_string& bpd_name,
        const rs_string& pron_conv_fname,
        const rs_string& pron_dict_fname);
    rs_string cd_ro_base_name;
    rs_string cd_pron_conv_fname;
    rs_string cd_pron_dict_fname;
    rs_string cd_affix_cg_fname;
    rs_string cd_proper_lc_fname;
    rs_string cd_sort_order;
    rs_affix  cd_aff;
    pobro cd_pob;
    rs_table cd_pron_rst;
    rs_string cd_conv_dir;
    rs_string cd_bpd_name;  // RO abbreviation, e.g. L"U1"
    bool cd_keep_to; // if true, respelled words from TO are kept in .ispell/.hash files
    bool cd_common_only; // if true, only words with positive frequencies 
        // are included in created RO dictionaries
    rs_string cd_rflag;  // required flag
    unsigned cd_min_freqx; // minimal value of FREQX in order to include word into output dictionary
    rs_string_v cd_kaffix_v; // vector of affixes that are kept
    rs_string cd_debugw; // word at which it is easy to place a breakpoint
};



void create_homo_table (const rs_string& dict_fname);
bool get_ro_dict_stat (const rs_string& dict_fname);


#endif
